import wx
import os
import cv2
import glob
import math
import threading
import numpy as np
from collections import deque
from docx import Document
from docx.shared import Cm
from docx.enum.section import WD_ORIENT
from PIL import Image
import gc





class Mp4CaptureApp(wx.Frame):
    def __init__(self, parent=None):
        wx.Frame.__init__(self, parent, title="MP4视频自动截图工具", size=(550, 650), 
                         style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
        # 设置窗口图标
        self.SetIcon(wx.Icon('C:/xp/output2.ico', wx.BITMAP_TYPE_ICO))
        self.panel = wx.Panel(self)
        self.layout_running = False
        
        # Initialize UI
        self.init_ui()
        
        # Center and show
        self.Centre()
        self.Show()
        
    def init_ui(self):
        """Initialize the user interface"""
        # Use BoxSizer for layout
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # ===== File Selection Area =====
        file_box = wx.StaticBox(self.panel, label="视频文件选择")
        file_sizer = wx.StaticBoxSizer(file_box, wx.HORIZONTAL)
        self.file_input = wx.TextCtrl(self.panel, style=wx.TE_READONLY, size=(300, -1))
        btn_add_video = wx.Button(self.panel, label="浏览...", size=(80, -1))
        btn_add_video.Bind(wx.EVT_BUTTON, lambda evt: self.AddMp4File(self.file_input))
        file_sizer.Add(self.file_input, 1, wx.EXPAND|wx.RIGHT, 5)
        file_sizer.Add(btn_add_video, 0, wx.EXPAND)
        main_sizer.Add(file_sizer, 0, wx.EXPAND|wx.ALL, 10)
        
        # Add separator
        main_sizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 10)
        
        # ===== Screenshot Mode Area =====
        mode_box = wx.StaticBox(self.panel, label="截图模式选择")
        mode_sizer = wx.StaticBoxSizer(mode_box, wx.VERTICAL)
        
        # Conservative mode
        self.chk_conservative = wx.RadioButton(self.panel, label="保守截图（保存每一帧）", style=wx.RB_GROUP)
        mode_sizer.Add(self.chk_conservative, 0, wx.ALL, 5)
        
        # Add separator
        mode_sizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        
        # Flexible mode
        flexible_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.chk_flexible = wx.RadioButton(self.panel, label="灵活截图（基于动态阈值）")
        flexible_sizer.Add(self.chk_flexible, 0, wx.ALL, 5)
        
        # Pre-check button
        self.btn_pre_check = wx.Button(self.panel, label="视频预检测", size=(120, -1))
        self.btn_pre_check.Bind(wx.EVT_BUTTON, lambda evt: self.PreCheckVideo(self.file_input))
        flexible_sizer.Add(self.btn_pre_check, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 10)
        mode_sizer.Add(flexible_sizer)
        
        # Flexible parameters (disabled by default)
        flex_param_sizer = wx.BoxSizer(wx.HORIZONTAL)
        flex_param_sizer.Add((20, 0))  # Indent
        flex_param_grid = wx.FlexGridSizer(cols=2, vgap=5, hgap=10)
        
        flex_param_grid.Add(wx.StaticText(self.panel, label="动态阈值系数:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.dynamic_threshold_factor = wx.SpinCtrlDouble(self.panel, value="2.0", min=0.5, max=5.0, inc=0.1, size=(80, -1))
        self.dynamic_threshold_factor.Disable()
        flex_param_grid.Add(self.dynamic_threshold_factor, 0, wx.EXPAND)
        
        flex_param_grid.Add(wx.StaticText(self.panel, label="最小变化量:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.min_change = wx.SpinCtrl(self.panel, value="10", min=1, max=100, size=(80, -1))
        self.min_change.Disable()
        flex_param_grid.Add(self.min_change, 0, wx.EXPAND)
        
        flex_param_sizer.Add(flex_param_grid, 1, wx.EXPAND)
        mode_sizer.Add(flex_param_sizer, 0, wx.EXPAND|wx.BOTTOM, 10)
        
        # Add separator
        mode_sizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        
        # Fixed interval mode
        self.chk_fixed = wx.RadioButton(self.panel, label="定帧截图（固定间隔帧）")
        mode_sizer.Add(self.chk_fixed, 0, wx.ALL, 5)
        
        fixed_param_sizer = wx.BoxSizer(wx.HORIZONTAL)
        fixed_param_sizer.Add((20, 0))  # Indent
        fixed_param_grid = wx.FlexGridSizer(cols=2, vgap=5, hgap=10)
        
        fixed_param_grid.Add(wx.StaticText(self.panel, label="帧间隔:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.fixed_interval = wx.SpinCtrl(self.panel, value="30", min=1, max=1000, size=(80, -1))
        fixed_param_grid.Add(self.fixed_interval, 0, wx.EXPAND)
        
        fixed_param_sizer.Add(fixed_param_grid, 1, wx.EXPAND)
        mode_sizer.Add(fixed_param_sizer, 0, wx.EXPAND|wx.BOTTOM, 10)
        
        # Add separator
        mode_sizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
        
        # Smart mode
        self.chk_smart = wx.RadioButton(self.panel, label="智能截图（自适应跳帧）")
        mode_sizer.Add(self.chk_smart, 0, wx.ALL, 5)
        
        smart_param_sizer = wx.BoxSizer(wx.HORIZONTAL)
        smart_param_sizer.Add((20, 0))  # Indent
        smart_param_grid = wx.FlexGridSizer(cols=2, vgap=5, hgap=10)
        
        smart_param_grid.Add(wx.StaticText(self.panel, label="最小步长:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.min_step = wx.SpinCtrl(self.panel, value="2", min=1, max=10, size=(80, -1))
        smart_param_grid.Add(self.min_step, 0, wx.EXPAND)
        
        smart_param_grid.Add(wx.StaticText(self.panel, label="最大步长:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.max_step = wx.SpinCtrl(self.panel, value="30", min=10, max=100, size=(80, -1))
        smart_param_grid.Add(self.max_step, 0, wx.EXPAND)
        
        smart_param_grid.Add(wx.StaticText(self.panel, label="变化阈值:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.change_threshold = wx.Slider(self.panel, value=25, minValue=10, maxValue=50, 
                                        style=wx.SL_HORIZONTAL|wx.SL_LABELS, size=(150, -1))
        smart_param_grid.Add(self.change_threshold, 0, wx.EXPAND)
        
        smart_param_grid.Add(wx.StaticText(self.panel, label="最小内容变化:"), 0, wx.ALIGN_CENTER_VERTICAL)
        self.min_content_change = wx.Slider(self.panel, value=20, minValue=10, maxValue=50, 
                                          style=wx.SL_HORIZONTAL|wx.SL_LABELS, size=(150, -1))
        smart_param_grid.Add(self.min_content_change, 0, wx.EXPAND)
        
        smart_param_sizer.Add(smart_param_grid, 1, wx.EXPAND)
        mode_sizer.Add(smart_param_sizer, 0, wx.EXPAND|wx.BOTTOM, 10)
        
        main_sizer.Add(mode_sizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)
        
        # ===== Action Buttons Area =====
        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        btn_capture = wx.Button(self.panel, label="开始截图", size=(120, -1))
        btn_capture.Bind(wx.EVT_BUTTON, lambda evt: self.AutoCaptureScreenshots(
            self.file_input,
            self.chk_conservative,
            self.chk_flexible,
            self.chk_fixed,
            self.chk_smart,
            {
                'dynamic_threshold_factor': self.dynamic_threshold_factor,
                'min_change': self.min_change,
                'fixed_interval': self.fixed_interval,
                'min_step': self.min_step,
                'max_step': self.max_step,
                'change_threshold': self.change_threshold,
                'min_content_change': self.min_content_change
            }
        ))
        btn_sizer.Add(btn_capture, 0, wx.RIGHT, 10)
        
        btn_layout_vertical = wx.Button(self.panel, label="按2行3列6图纵向排版", size=(180, -1))
        btn_layout_vertical.Bind(wx.EVT_BUTTON, self.OnLayoutVertical)
        btn_sizer.Add(btn_layout_vertical, 0)
        btn_sizer.AddSpacer(10)
        main_sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER|wx.BOTTOM, 10)

        # ===== Progress Bar Area =====
        self.pre_check_progress_bar = wx.Gauge(self.panel, range=100, size=(-1, 20), 
                                            style=wx.GA_HORIZONTAL|wx.GA_SMOOTH)
        main_sizer.Add(self.pre_check_progress_bar, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)
        
        # Status text
        self.status_text = wx.StaticText(self.panel, label="准备就绪")
        main_sizer.Add(self.status_text, 0, wx.ALIGN_CENTER|wx.BOTTOM, 10)
        
        self.panel.SetSizer(main_sizer)
    
    def AddMp4File(self, file_input):
        wildcard = "MP4 files (*.mp4)|*.mp4"
        dialog = wx.FileDialog(self, "选择MP4文件", wildcard=wildcard, style=wx.FD_OPEN)
        if dialog.ShowModal() == wx.ID_OK:
            path = dialog.GetPath()
            file_input.SetValue(path)
        dialog.Destroy()
    
    def PreCheckVideo(self, file_input):
        video_path = file_input.GetValue()
        if not video_path or not os.path.exists(video_path):
            wx.MessageBox("请先添加有效的MP4视频文件！", "错误", wx.OK | wx.ICON_ERROR)
            return

        try:
            cap = cv2.VideoCapture(video_path)
            if not cap.isOpened():
                wx.MessageBox("无法打开视频文件，请检查文件格式或路径！", "错误", wx.OK | wx.ICON_ERROR)
                return

            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            fps = cap.get(cv2.CAP_PROP_FPS)
            frame_change_speeds = []

            self.pre_check_progress_bar.SetRange(total_frames)
            print(f"开始视频预检测，总帧数：{total_frames}，帧率：{fps}")

            batch_size = 100
            prev_frame = None

            for i in range(0, total_frames, batch_size):
                end_frame = min(i + batch_size, total_frames)
                frames = []
                for frame_idx in range(i, end_frame):
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                    ret, frame = cap.read()
                    if not ret:
                        break
                    frames.append(frame)
                
                for j, frame in enumerate(frames):
                    current_frame_idx = i + j
                    if prev_frame is not None:
                        diff = cv2.absdiff(prev_frame, frame)
                        change_speed = diff.mean()
                        frame_change_speeds.append(change_speed)
                    prev_frame = frame
                    self.pre_check_progress_bar.SetValue(current_frame_idx)
                    wx.Yield()
                
                del frames
                gc.collect()

            avg_change_speed = sum(frame_change_speeds)/len(frame_change_speeds) if frame_change_speeds else 0
            self.flexible_threshold = avg_change_speed * float(self.dynamic_threshold_factor.GetValue())
            print(f"预检测完成！平均变化速度：{avg_change_speed:.2f}，动态阈值：{self.flexible_threshold:.2f}")
            wx.MessageBox(f"视频预检测完成！\n平均画面变化速度：{avg_change_speed:.2f}\n动态阈值：{self.flexible_threshold:.2f}", 
                         "提示", wx.OK | wx.ICON_INFORMATION)
            
            self.frame_change_speeds = frame_change_speeds

        finally:
            if 'cap' in locals() and cap.isOpened():
                cap.release()
            self.pre_check_progress_bar.SetValue(0)

    def AutoCaptureScreenshots(self, file_input, chk_conservative, chk_flexible, chk_fixed, chk_smart, parameters):
        selected_modes = [chk_conservative.GetValue(), 
                         chk_flexible.GetValue(),
                         chk_fixed.GetValue(),
                         chk_smart.GetValue()]
        if sum(selected_modes) != 1:
            wx.MessageBox("请选择且仅选择一个截图模式！", "错误", wx.OK | wx.ICON_ERROR)
            return

        video_path = file_input.GetValue()
        if not video_path or not os.path.exists(video_path):
            wx.MessageBox("请先添加有效的MP4视频文件！", "错误", wx.OK | wx.ICON_ERROR)
            return

        output_dir = os.path.join(os.getcwd(), "mp4tojpg")
        try:
            os.makedirs(output_dir, exist_ok=True)
        except Exception as e:
            wx.MessageBox(f"创建目录失败：{str(e)}", "错误", wx.OK | wx.ICON_ERROR)
            return

        try:
            cap = cv2.VideoCapture(video_path)
            if not cap.isOpened():
                wx.MessageBox("无法打开视频文件！", "错误", wx.OK | wx.ICON_ERROR)
                return

            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            self.pre_check_progress_bar.SetRange(total_frames)
            saved_count = 0

            # Smart capture mode
            if chk_smart.GetValue():
                min_step = parameters['min_step'].GetValue()
                max_step = parameters['max_step'].GetValue()
                change_threshold = parameters['change_threshold'].GetValue() / 100.0
                min_content_change = parameters['min_content_change'].GetValue() / 100.0
                
                current_step = min_step
                prev_frame = None
                change_history = deque(maxlen=10)
                last_saved_idx = -1

                frame_idx = 0
                while frame_idx < total_frames:
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                    ret, frame = cap.read()
                    if not ret:
                        break

                    save_flag = False
                    
                    if last_saved_idx == -1:
                        save_flag = True
                    else:
                        change_rate = self.get_smart_difference(prev_frame, frame)
                        change_history.append(change_rate)
                        
                        avg_change = np.mean(change_history) if change_history else 0
                        if avg_change > change_threshold * 1.5:
                            current_step = max(min_step, int(current_step * 0.6))
                        elif avg_change < change_threshold * 0.5:
                            current_step = min(max_step, int(current_step * 1.5))
                        
                        must_save = (change_rate > min_content_change) or ((frame_idx - last_saved_idx) >= max_step)
                        suggest_save = (change_rate > change_threshold) and ((frame_idx - last_saved_idx) >= min_step)
                        
                        if must_save or suggest_save:
                            save_flag = True

                    if save_flag:
                        output_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.jpg")
                        cv2.imwrite(output_path, frame)
                        saved_count += 1
                        last_saved_idx = frame_idx
                        prev_frame = frame.copy()

                    self.pre_check_progress_bar.SetValue(frame_idx)
                    wx.Yield()
                    
                    frame_idx += current_step

                    if frame_idx >= total_frames - 1 and last_saved_idx != total_frames - 1:
                        cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames - 1)
                        ret, frame = cap.read()
                        if ret:
                            output_path = os.path.join(output_dir, f"frame_{total_frames-1:06d}.jpg")
                            cv2.imwrite(output_path, frame)
                            saved_count += 1

            # Conservative mode
            elif chk_conservative.GetValue():
                frame_idx = 0
                while frame_idx < total_frames:
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                    ret, frame = cap.read()
                    if not ret:
                        break

                    output_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.jpg")
                    cv2.imwrite(output_path, frame)
                    saved_count += 1

                    self.pre_check_progress_bar.SetValue(frame_idx)
                    wx.Yield()
                    
                    frame_idx += 1

            # Flexible mode
            elif chk_flexible.GetValue():
                frame_idx = 0
                prev_frame = None
                while frame_idx < total_frames:
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                    ret, frame = cap.read()
                    if not ret:
                        break

                    save_flag = False
                    
                    if prev_frame is not None:
                        diff = cv2.absdiff(prev_frame, frame)
                        change_speed = diff.mean()
                        if change_speed > self.flexible_threshold:
                            save_flag = True
                    else:
                        save_flag = True  # Save first frame
                    
                    if save_flag:
                        output_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.jpg")
                        cv2.imwrite(output_path, frame)
                        saved_count += 1
                        prev_frame = frame.copy()

                    self.pre_check_progress_bar.SetValue(frame_idx)
                    wx.Yield()
                    
                    frame_idx += 1

            # Fixed interval mode
            elif chk_fixed.GetValue():
                fixed_interval = parameters['fixed_interval'].GetValue()
                frame_idx = 0
                while frame_idx < total_frames:
                    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                    ret, frame = cap.read()
                    if not ret:
                        break

                    if frame_idx % fixed_interval == 0 or frame_idx == total_frames - 1:
                        output_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.jpg")
                        cv2.imwrite(output_path, frame)
                        saved_count += 1

                    self.pre_check_progress_bar.SetValue(frame_idx)
                    wx.Yield()
                    
                    frame_idx += 1

            wx.MessageBox(f"截图完成！共保存 {saved_count} 张图片到：\n{output_dir}",
                         "成功", wx.OK | wx.ICON_INFORMATION)
        finally:
            if cap.isOpened():
                cap.release()
            self.pre_check_progress_bar.SetValue(0)

    def get_smart_difference(self, frame1, frame2):
        """Calculate smart difference between frames"""
        if frame1 is None or frame2 is None:
            return 1.0
        
        hsv1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
        hsv2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2HSV)
        small1 = cv2.resize(hsv1, (64, 64))
        small2 = cv2.resize(hsv2, (64, 64))
        diff = cv2.absdiff(small1, small2)
        diff = cv2.GaussianBlur(diff, (5,5), 0)
        pixel_diff = np.mean(diff) / 255.0
        
        gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
        gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
        lbp1 = self.local_binary_pattern(gray1)
        lbp2 = self.local_binary_pattern(gray2)
        lbp_diff = np.sum(lbp1 != lbp2) / lbp1.size
        
        flow = cv2.calcOpticalFlowFarneback(gray1, gray2, None, 0.5, 3, 15, 3, 5, 1.2, 0)
        motion = np.sqrt(flow[...,0]**2 + flow[...,1]**2)
        motion_diff = np.mean(motion > 1.0)
        
        combined_diff = (pixel_diff * 0.4 + lbp_diff * 0.4 + motion_diff * 0.2)
        return min(combined_diff, 1.0)

    def local_binary_pattern(self, image, radius=3, neighbors=8):
        """Calculate local binary pattern"""
        lbp = np.zeros_like(image)
        for i in range(radius, image.shape[0]-radius):
            for j in range(radius, image.shape[1]-radius):
                center = image[i,j]
                values = []
                for k in range(neighbors):
                    x = i + int(radius * math.cos(2*math.pi*k/neighbors))
                    y = j + int(radius * math.sin(2*math.pi*k/neighbors))
                    values.append(1 if image[x,y] > center else 0)
                lbp[i,j] = sum([v * (2**n) for n,v in enumerate(values)])
        return lbp

    def OnLayoutVertical(self, event):
        """2 rows x 3 columns vertical layout"""
        self.run_layout_processing(
            layout_mode="vertical",
            rows=2,
            cols=3,
            margins=(Cm(1.7), Cm(1), Cm(3), Cm(2)),
            orientation=WD_ORIENT.PORTRAIT,
            output_filename="纵向排版.docx"
        )

    def run_layout_processing(self, layout_mode, rows, cols, margins, orientation, output_filename):
        """Start layout processing in background thread"""
        image_dir = "mp4tojpg"
        if not os.path.exists(image_dir):
            wx.MessageBox(f"文件夹 '{image_dir}' 不存在！", "错误", wx.OK | wx.ICON_ERROR)
            return

        image_files = sorted(glob.glob(os.path.join(image_dir, "*.png")) + glob.glob(os.path.join(image_dir, "*.jpg")))
        if not image_files:
            wx.MessageBox(f"文件夹 '{image_dir}' 中没有图片文件！", "提示", wx.OK | wx.ICON_INFORMATION)
            return

        try:
            self.status_text.SetLabel("正在后台处理图片，请稍候...")
            self.layout_running = True
            self.Bind(wx.EVT_CLOSE, self.on_close_for_layout)

            threading.Thread(
                target=self.generate_doc_in_background,
                args=(image_files, layout_mode, rows, cols, margins, orientation, output_filename),
                daemon=True
            ).start()
        except Exception as e:
            wx.MessageBox(f"发生错误: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)
            self.status_text.SetLabel("排版失败")

    def generate_doc_in_background(self, image_files, layout_mode, rows, cols, margins, orientation, output_filename):
        """Generate document in background thread"""
        try:
            doc = Document()
            section = doc.sections[0]
            section.orientation = orientation
            section.left_margin = margins[0]
            section.right_margin = margins[1]
            section.top_margin = margins[2]
            section.bottom_margin = margins[3]

            page_width = section.page_width - section.left_margin - section.right_margin
            page_height = section.page_height - section.top_margin - section.bottom_margin

            cell_width = page_width / cols
            cell_height = page_height / rows

            total_images = len(image_files)
            processed_images = 0

            for i in range(0, total_images, rows * cols):
                if not getattr(self, "layout_running", False):
                    wx.CallAfter(self.status_text.SetLabel, "排版已取消")
                    return

                table = doc.add_table(rows=rows, cols=cols)
                table.autofit = False

                for row in range(rows):
                    for col in range(cols):
                        idx = i + row * cols + col
                        if idx >= total_images:
                            break

                        img_path = image_files[idx]
                        img = Image.open(img_path)
                        img_aspect_ratio = img.width / img.height
                        cell_aspect_ratio = cell_width / cell_height

                        if img_aspect_ratio > cell_aspect_ratio:
                            img_width = cell_width
                            img_height = cell_width / img_aspect_ratio
                        else:
                            img_height = cell_height
                            img_width = cell_height * img_aspect_ratio

                        cell = table.cell(row, col)
                        paragraph = cell.paragraphs[0]
                        run = paragraph.add_run()
                        run.add_picture(img_path, width=img_width, height=img_height)

                processed_images += rows * cols
                progress_message = f"已处理 {min(processed_images, total_images)}/{total_images} 图片"
                wx.CallAfter(self.status_text.SetLabel, progress_message)

            output_path = os.path.join(os.getcwd(), output_filename)
            doc.save(output_path)

            wx.CallAfter(wx.MessageBox, f"文档已成功生成，保存为：{output_path}", "完成", wx.OK | wx.ICON_INFORMATION)
            wx.CallAfter(self.status_text.SetLabel, "排版完成")
        except Exception as e:
            error_message = f"发生错误: {str(e)}"
            wx.CallAfter(wx.MessageBox, error_message, "错误", wx.OK | wx.ICON_ERROR)
            wx.CallAfter(self.status_text.SetLabel, "排版失败")
        finally:
            self.Unbind(wx.EVT_CLOSE, handler=self.on_close_for_layout)

    def on_close_for_layout(self, event):
        """Handle window close event during layout processing"""
        self.layout_running = False
        if hasattr(self, "thread") and self.thread.is_alive():
            self.thread.join()
        self.Destroy()

if __name__ == "__main__":
    app = wx.App(False)
    frame = Mp4CaptureApp()
    app.MainLoop()
